﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml;

namespace SmoothStreaming.PerformanceTesting
{
    public delegate void PerformanceTestUpdateInfoEvent(SmoothStreamPerformanceTest test, string message);
    public delegate void ChunkTestedEvent(SmoothStreamPerformanceTest test, SmoothStreamChunk chunk, ChunkResponse chunkResponse);

    public class SmoothStreamPerformanceTest
    {
        public event PerformanceTestUpdateInfoEvent PerformanceTestUpdated;
        public event ChunkTestedEvent ChunkTested;

        private Uri _manifestUri;
        private bool _testRunning = false;
        private bool _stopRequested = false;

        private List<StreamIndex> _streamIndexes;
        private Dictionary<SmoothStreamChunk, ChunkResponse> _responses = new Dictionary<SmoothStreamChunk, ChunkResponse>();

        public TimeSpan? ManifestDownloadTime { get; private set; }
        public TimeSpan TimeIntervalBetweenChunks { get; set; }

        List<SmoothStreamChunk> _chunkList = new List<SmoothStreamChunk>();

        public SmoothStreamPerformanceTest(Uri manifestUri)
        {
            _manifestUri = manifestUri;
            TimeIntervalBetweenChunks = TimeSpan.FromMilliseconds(100);
        }

        public IEnumerable<StreamIndex> StreamIndexes { get { return _streamIndexes; } }

        public int TotalChunks
        {
            get
            {
                return _chunkList.Count;
            }
        }

        public int ChunksTested
        {
            get
            {
                return _responses.Keys.Count;
            }
        }

        public TimeSpan AverageAudioResponseTime
        {
            get
            {
                return CalculateAverageResponseTime(_responses.Where(kp => kp.Key.StreamType.ToUpperInvariant() == "AUDIO").Select(kp => kp.Value));
            }
        }

        public TimeSpan AverageVideoResponseTime
        {
            get
            {
                return CalculateAverageResponseTime(_responses.Where(kp => kp.Key.StreamType.ToUpperInvariant() == "VIDEO").Select(kp => kp.Value));
            }
        }

        private TimeSpan CalculateAverageResponseTime(IEnumerable<ChunkResponse> responses)
        {
            var validResponses = responses.Where(r => r != null && r.Elapsed.HasValue).ToList();

            if (validResponses.Count == 0) { return TimeSpan.FromMilliseconds(0); }

            var avgMs = validResponses.Average(r => r.Elapsed.Value.TotalMilliseconds);

            return TimeSpan.FromMilliseconds(avgMs);
        }

        public bool IsRunning { get { return _testRunning; } }
        
        public void StartAsync()
        {
            if (_testRunning) { throw new Exception("Test is already running."); }

            _stopRequested = false;
            _testRunning = true;
            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(TestThread));
        }

        public void RunTestSynchronous()
        {
            _stopRequested = false;
            _testRunning = true;
            TestThread(null);
        }

        private void TestThread(object state)
        {
            RunTestSteps();

            _testRunning = false;
        }

        private void RunTestSteps()
        {
            Log("Retrieving manifest...");
            try
            {
                ParseManifest();
            }
            catch (Exception ex)
            {
                FlagTestError("Manifest Parser Error", ex);
                return;
            }

            Log("Testing chunk downloads...");
            try
            {
                DownloadChunks();
            }
            catch (Exception ex)
            {
                FlagTestError("Download Chunks Error", ex);
                return;
            }
        }

        private void DownloadChunks()
        {
            _responses.Clear();
            Log("Building chunk list...");
            _chunkList.Clear();

            foreach (var si in _streamIndexes)
            {
                foreach (var c in si.Chunks)
                {
                    _chunkList.Add(c);
                }
            }

            _chunkList = _chunkList.OrderBy(c => c.Bitrate).ThenBy(c => c.FragmentIndex).ToList();

            Log(_chunkList.Count.ToString() + " possible chunks total");

            int chunksTested = 0;

            for (int i = 0; i < _chunkList.Count; i++)
            {
                if (_stopRequested)
                {
                    Log("Stop requested, aborting rest of test.");
                    return;
                }

                Log("Testing chunk " + (i + 1).ToString() + " of " + _chunkList.Count.ToString() + " (" + _chunkList[i].StreamType + " / " + (_chunkList[i].Bitrate / 1000).ToString() + "kbps" + ") ...");
                try
                {
                    var result = TestChunk(_chunkList[i]);

                    Log(" .. Response: " + (result.Elapsed.HasValue ? System.Math.Round(result.Elapsed.Value.TotalMilliseconds, 0).ToString() + "ms" : "unknown") + ", status code " + result.ReturnStatusCodeNumeric.ToString());
                }
                catch (Exception ex)
                {
                    if (!_responses.ContainsKey(_chunkList[i]))
                    {
                        _chunkList[i] = null;
                    }

                    Log("Chunk error (" + _chunkList[i].ChunkUrl + "): " + ex.Message);
                }

                chunksTested += 1;

                System.Threading.Thread.Sleep(Convert.ToInt32(TimeIntervalBetweenChunks.TotalMilliseconds));
            }
        }

        private ChunkResponse TestChunk(SmoothStreamChunk chunk)
        {
            ChunkRetriever cr = new ChunkRetriever();
            var response = cr.DownloadChunkStatsOnly(chunk);

            _responses[chunk] = response;

            if (ChunkTested != null)
            {
                try
                {
                    this.ChunkTested(this, chunk, response);
                }
                catch (Exception ex)
                {
                    Log("Exception raising chunk tested event: " + ex.Message);
                }
            }

            return response;
        }

        private void Log(string msg)
        {
            Console.WriteLine(msg);

            if (this.PerformanceTestUpdated != null)
            {
                try
                {
                    this.PerformanceTestUpdated(this, msg);
                }
                catch (Exception)
                {

                }
            }
        }

        private void FlagTestError(string error, Exception ex)
        {
            Log("Error: " + error + ": " + ex.Message);
        }

        private void ParseManifest()
        {
            ManifestParser mp = new ManifestParser();
            _streamIndexes = mp.ParseManifest(_manifestUri).ToList();

            ManifestDownloadTime = mp.ManifestDownloadTime;
        }

        public void Stop()
        {
            _stopRequested = true;
        }
    }
}
